// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>

#include "base/pickle.h"
#include "build/build_config.h"
#include "content/common/cursors/webcursor.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/public/platform/WebCursorInfo.h"
#include "third_party/skia/include/core/SkImageInfo.h"

#if defined(OS_WIN)
#include <windows.h>
#endif

using blink::WebCursorInfo;

namespace content {

TEST(WebCursorTest, OKCursorSerialization)
{
    WebCursor custom_cursor;
    // This is a valid custom cursor.
    base::Pickle ok_custom_pickle;
    // Type and hotspots.
    ok_custom_pickle.WriteInt(WebCursorInfo::TypeCustom);
    ok_custom_pickle.WriteInt(0);
    ok_custom_pickle.WriteInt(0);
    // X & Y
    ok_custom_pickle.WriteInt(1);
    ok_custom_pickle.WriteInt(1);
    // Scale
    ok_custom_pickle.WriteFloat(1.0);
    // Data len including enough data for a 1x1 image.
    ok_custom_pickle.WriteInt(4);
    ok_custom_pickle.WriteUInt32(0);
    // Custom Windows message.
    ok_custom_pickle.WriteUInt32(0);
    base::PickleIterator iter(ok_custom_pickle);
    EXPECT_TRUE(custom_cursor.Deserialize(&iter));
}

TEST(WebCursorTest, BrokenCursorSerialization)
{
    WebCursor custom_cursor;
    // This custom cursor has not been send with enough data.
    base::Pickle short_custom_pickle;
    // Type and hotspots.
    short_custom_pickle.WriteInt(WebCursorInfo::TypeCustom);
    short_custom_pickle.WriteInt(0);
    short_custom_pickle.WriteInt(0);
    // X & Y
    short_custom_pickle.WriteInt(1);
    short_custom_pickle.WriteInt(1);
    // Scale
    short_custom_pickle.WriteFloat(1.0);
    // Data len not including enough data for a 1x1 image.
    short_custom_pickle.WriteInt(3);
    short_custom_pickle.WriteUInt32(0);
    base::PickleIterator iter(short_custom_pickle);
    EXPECT_FALSE(custom_cursor.Deserialize(&iter));

    // This custom cursor has enough data but is too big.
    base::Pickle large_custom_pickle;
    // Type and hotspots.
    large_custom_pickle.WriteInt(WebCursorInfo::TypeCustom);
    large_custom_pickle.WriteInt(0);
    large_custom_pickle.WriteInt(0);
    // X & Y
    static const int kTooBigSize = 4096 + 1;
    large_custom_pickle.WriteInt(kTooBigSize);
    large_custom_pickle.WriteInt(1);
    // Scale
    large_custom_pickle.WriteFloat(1.0);
    // Data len including enough data for a 4097x1 image.
    large_custom_pickle.WriteInt(kTooBigSize * 4);
    for (int i = 0; i < kTooBigSize; ++i)
        large_custom_pickle.WriteUInt32(0);
    iter = base::PickleIterator(large_custom_pickle);
    EXPECT_FALSE(custom_cursor.Deserialize(&iter));

    // This custom cursor uses negative lengths.
    base::Pickle neg_custom_pickle;
    // Type and hotspots.
    neg_custom_pickle.WriteInt(WebCursorInfo::TypeCustom);
    neg_custom_pickle.WriteInt(0);
    neg_custom_pickle.WriteInt(0);
    // X & Y
    neg_custom_pickle.WriteInt(-1);
    neg_custom_pickle.WriteInt(-1);
    // Scale
    neg_custom_pickle.WriteFloat(1.0);
    // Data len including enough data for a 1x1 image.
    neg_custom_pickle.WriteInt(4);
    neg_custom_pickle.WriteUInt32(0);
    // Custom Windows message.
    neg_custom_pickle.WriteUInt32(0);
    iter = base::PickleIterator(neg_custom_pickle);
    EXPECT_FALSE(custom_cursor.Deserialize(&iter));

    // This custom cursor uses zero scale.
    base::Pickle scale_zero_custom_pickle;
    // Type and hotspots.
    scale_zero_custom_pickle.WriteInt(WebCursorInfo::TypeCustom);
    scale_zero_custom_pickle.WriteInt(0);
    scale_zero_custom_pickle.WriteInt(0);
    // X & Y
    scale_zero_custom_pickle.WriteInt(1);
    scale_zero_custom_pickle.WriteInt(1);
    // Scale
    scale_zero_custom_pickle.WriteFloat(0);
    // Data len including enough data for a 1x1 image.
    scale_zero_custom_pickle.WriteInt(4);
    scale_zero_custom_pickle.WriteUInt32(0);
    // Custom Windows message.
    scale_zero_custom_pickle.WriteUInt32(0);
    iter = base::PickleIterator(scale_zero_custom_pickle);
    EXPECT_FALSE(custom_cursor.Deserialize(&iter));

    // This custom cursor uses tiny scale.
    base::Pickle scale_tiny_custom_pickle;
    // Type and hotspots.
    scale_tiny_custom_pickle.WriteInt(WebCursorInfo::TypeCustom);
    scale_tiny_custom_pickle.WriteInt(0);
    scale_tiny_custom_pickle.WriteInt(0);
    // X & Y
    scale_tiny_custom_pickle.WriteInt(1);
    scale_tiny_custom_pickle.WriteInt(1);
    // Scale
    scale_tiny_custom_pickle.WriteFloat(0.001f);
    // Data len including enough data for a 1x1 image.
    scale_tiny_custom_pickle.WriteInt(4);
    scale_tiny_custom_pickle.WriteUInt32(0);
    // Custom Windows message.
    scale_tiny_custom_pickle.WriteUInt32(0);
    iter = base::PickleIterator(scale_tiny_custom_pickle);
    EXPECT_FALSE(custom_cursor.Deserialize(&iter));
}

TEST(WebCursorTest, ClampHotspot)
{
    WebCursor custom_cursor;
    // This is a valid custom cursor.
    base::Pickle ok_custom_pickle;
    // Type and hotspots.
    ok_custom_pickle.WriteInt(WebCursorInfo::TypeCustom);
    // Hotspot is invalid --- outside the bounds of the image.
    ok_custom_pickle.WriteInt(5);
    ok_custom_pickle.WriteInt(5);
    // X & Y
    ok_custom_pickle.WriteInt(2);
    ok_custom_pickle.WriteInt(2);
    // Scale
    ok_custom_pickle.WriteFloat(1.0);
    // Data len including enough data for a 2x2 image.
    ok_custom_pickle.WriteInt(4 * 4);
    for (size_t i = 0; i < 4; i++)
        ok_custom_pickle.WriteUInt32(0);
    // Custom Windows message.
    ok_custom_pickle.WriteUInt32(0);
    base::PickleIterator iter(ok_custom_pickle);
    EXPECT_TRUE(custom_cursor.Deserialize(&iter));

    // Convert to WebCursorInfo, make sure the hotspot got clamped.
    WebCursor::CursorInfo info;
    custom_cursor.GetCursorInfo(&info);
    EXPECT_EQ(gfx::Point(1, 1), info.hotspot);

    // Set hotspot to an invalid point again, pipe back through WebCursor,
    // and make sure the hotspot got clamped again.
    info.hotspot = gfx::Point(-1, -1);
    custom_cursor.InitFromCursorInfo(info);
    custom_cursor.GetCursorInfo(&info);
    EXPECT_EQ(gfx::Point(0, 0), info.hotspot);
}

TEST(WebCursorTest, EmptyImage)
{
    WebCursor custom_cursor;
    base::Pickle broken_cursor_pickle;
    broken_cursor_pickle.WriteInt(WebCursorInfo::TypeCustom);
    // Hotspot is at origin
    broken_cursor_pickle.WriteInt(0);
    broken_cursor_pickle.WriteInt(0);
    // X & Y are empty
    broken_cursor_pickle.WriteInt(0);
    broken_cursor_pickle.WriteInt(0);
    // Scale
    broken_cursor_pickle.WriteFloat(1.0);
    // No data for the image since the size is 0.
    broken_cursor_pickle.WriteInt(0);
    // Custom Windows message.
    broken_cursor_pickle.WriteInt(0);

    // Make sure we can read this on all platforms; it is technicaally a valid
    // cursor.
    base::PickleIterator iter(broken_cursor_pickle);
    EXPECT_TRUE(custom_cursor.Deserialize(&iter));
}

TEST(WebCursorTest, Scale2)
{
    WebCursor custom_cursor;
    // This is a valid custom cursor.
    base::Pickle ok_custom_pickle;
    // Type and hotspots.
    ok_custom_pickle.WriteInt(WebCursorInfo::TypeCustom);
    ok_custom_pickle.WriteInt(0);
    ok_custom_pickle.WriteInt(0);
    // X & Y
    ok_custom_pickle.WriteInt(1);
    ok_custom_pickle.WriteInt(1);
    // Scale - 2 image pixels per UI pixel.
    ok_custom_pickle.WriteFloat(2.0);
    // Data len including enough data for a 1x1 image.
    ok_custom_pickle.WriteInt(4);
    ok_custom_pickle.WriteUInt32(0);
    // Custom Windows message.
    ok_custom_pickle.WriteUInt32(0);
    base::PickleIterator iter(ok_custom_pickle);
    EXPECT_TRUE(custom_cursor.Deserialize(&iter));
}

TEST(WebCursorTest, AlphaConversion)
{
    SkBitmap bitmap;
    SkPMColor testColor = SkPreMultiplyARGB(10, 255, 255, 255);
    bitmap.allocN32Pixels(1, 1);
    SkAutoLockPixels bitmap_lock(bitmap);
    *bitmap.getAddr32(0, 0) = testColor;
    WebCursor::CursorInfo cursor_info;
    cursor_info.type = WebCursorInfo::TypeCustom;
    cursor_info.custom_image = bitmap;
    cursor_info.image_scale_factor = 1;
    WebCursor custom_cursor;

    // This round trip will convert the cursor to unpremultiplied form.
    custom_cursor.InitFromCursorInfo(cursor_info);
    custom_cursor.GetCursorInfo(&cursor_info);
    {
        SkAutoLockPixels lock(cursor_info.custom_image);
        EXPECT_EQ(kUnpremul_SkAlphaType, cursor_info.custom_image.alphaType());
        EXPECT_EQ(testColor,
            SkPreMultiplyColor(*cursor_info.custom_image.getAddr32(0, 0)));
    }

    // Second round trip should not do any conversion because data is already
    // unpremultiplied.
    custom_cursor.InitFromCursorInfo(cursor_info);
    custom_cursor.GetCursorInfo(&cursor_info);
    {
        SkAutoLockPixels lock(cursor_info.custom_image);
        EXPECT_EQ(kUnpremul_SkAlphaType, cursor_info.custom_image.alphaType());
        EXPECT_EQ(testColor,
            SkPreMultiplyColor(*cursor_info.custom_image.getAddr32(0, 0)));
    }

#if defined(OS_MACOSX)
    // On MacOS, test roundtrip through NSCursor conversion.
    WebCursor custom_cursor_copy;
    custom_cursor_copy.InitFromNSCursor(custom_cursor.GetNativeCursor());
    custom_cursor_copy.GetCursorInfo(&cursor_info);
    {
        SkAutoLockPixels lock(cursor_info.custom_image);
        EXPECT_EQ(kUnpremul_SkAlphaType, cursor_info.custom_image.alphaType());
        EXPECT_EQ(testColor,
            SkPreMultiplyColor(*cursor_info.custom_image.getAddr32(0, 0)));
    }
#endif
}

#if defined(USE_AURA)
TEST(WebCursorTest, CursorScaleFactor)
{
    display::Display display;
    display.set_device_scale_factor(80.2f);

    WebCursor::CursorInfo info;
    info.image_scale_factor = 2.0f;

    WebCursor cursor;
    cursor.InitFromCursorInfo(info);
    cursor.SetDisplayInfo(display);

    EXPECT_EQ(40.1f, cursor.GetCursorScaleFactor());
}

TEST(WebCursorTest, UnscaledImageCopy)
{
    WebCursor::CursorInfo info;
    info.type = WebCursorInfo::TypeCustom;
    info.hotspot = gfx::Point(0, 1);

    SkImageInfo image_info = SkImageInfo::MakeN32(2, 2, kUnpremul_SkAlphaType);
    info.custom_image = SkBitmap();
    info.custom_image.setInfo(image_info);
    info.custom_image.allocN32Pixels(2, 2);
    info.custom_image.eraseColor(0xFFFFFFFF);

    WebCursor cursor;
    cursor.InitFromCursorInfo(info);

    SkBitmap image_copy;
    gfx::Point hotspot;
    cursor.CreateScaledBitmapAndHotspotFromCustomData(&image_copy, &hotspot);

    EXPECT_EQ(kBGRA_8888_SkColorType, image_copy.colorType());
    EXPECT_EQ(kUnpremul_SkAlphaType, image_copy.alphaType());
    EXPECT_EQ(2, image_copy.width());
    EXPECT_EQ(2, image_copy.height());
    EXPECT_EQ(0, hotspot.x());
    EXPECT_EQ(1, hotspot.y());
}

TEST(WebCursorTest, CopyDeviceScaleFactor)
{
    WebCursor cursor1;
    EXPECT_EQ(1.f, cursor1.GetCursorScaleFactor());

    display::Display display;
    display.set_device_scale_factor(19.333f);
    cursor1.SetDisplayInfo(display);
    WebCursor cursor2 = cursor1;
    EXPECT_EQ(19.333f, cursor2.GetCursorScaleFactor());
}
#endif

#if defined(OS_WIN)
namespace {

    void ScaleCursor(float scale_factor, int hotspot_x, int hotspot_y)
    {
        display::Display display;
        display.set_device_scale_factor(scale_factor);

        WebCursor::CursorInfo info;
        info.type = WebCursorInfo::TypeCustom;
        info.hotspot = gfx::Point(hotspot_x, hotspot_y);

        info.custom_image = SkBitmap();
        info.custom_image.allocN32Pixels(10, 10);
        info.custom_image.eraseColor(0);

        WebCursor cursor;
        cursor.SetDisplayInfo(display);
        cursor.InitFromCursorInfo(info);

        HCURSOR windows_cursor_handle = cursor.GetPlatformCursor();
        EXPECT_NE(nullptr, windows_cursor_handle);
        ICONINFO windows_icon_info;
        EXPECT_TRUE(GetIconInfo(windows_cursor_handle, &windows_icon_info));
        EXPECT_FALSE(windows_icon_info.fIcon);
        EXPECT_EQ(static_cast<DWORD>(scale_factor * hotspot_x),
            windows_icon_info.xHotspot);
        EXPECT_EQ(static_cast<DWORD>(scale_factor * hotspot_y),
            windows_icon_info.yHotspot);
    }

} // namespace

TEST(WebCursorTest, WindowsCursorScaledAtHiDpi)
{
    ScaleCursor(2.0f, 4, 6);
    ScaleCursor(1.5f, 2, 8);
    ScaleCursor(1.25f, 3, 7);
}
#endif

} // namespace content
